﻿using System;
using System.IO.Ports;
using System.Linq;
using System.Text;
using System.Threading;

namespace MDK.Device.Components
{
    public interface IScanner : IPlcDeviceComponent
    {
        void Trigger();
        string WaitCompleted();
        void Cancel();
        event CommunicationErrorHandler CommunicationError;
    }

    public class SerialPortScanner : IScanner
    {
        private string[] _portNames;

        private SerialPort[] _dataPorts;

        private PlcAddress _triggerAddr;

        public TimeSpan Timeout { get; set; }

        public int BarcodeLength { get; set; }

        public SerialPortScanner(IPlcDevice device, PlcAddress triggerAddr, int codeLen, params string[] portNames)
        {
            Device = device;
            _triggerAddr = triggerAddr;
            _portNames = portNames;
            _dataPorts = new SerialPort[portNames == null ? 0 : portNames.Length];
            BarcodeLength = codeLen;
            Timeout = TimeSpan.FromSeconds(4);
        }

        public IPlcDevice Device { get; }

        public void Trigger()
        {
            var retryCount = 0;
            var errorcode = 0;
            while (true)
            {
                for (int i = 0; i < _dataPorts.Length; i++)
                {
                    if (_dataPorts[i] == null)
                    {
                        if (!SerialPort.GetPortNames().Contains(_portNames[i]))
                        {
                            errorcode = ErrorCodeHelper.SerialPortNotExist;
                        }
                        else
                        {
                            _dataPorts[i] = new SerialPort(_portNames[i]);
                            try
                            {
                                _dataPorts[i].Open();
                            }
                            catch (Exception)
                            {
                                errorcode = ErrorCodeHelper.SerialPortOccupied;
                                _dataPorts[i].Close();
                                _dataPorts[i] = null;
                            }
                        }
                    }

                    if (errorcode != 0)
                    {
                        break;
                    }
                }

                if (errorcode == 0)
                {
                    foreach (var port in _dataPorts)
                    {
                        port.DiscardInBuffer();
                    }
                    break;
                }
                var retry = OnCommunicationError(errorcode, retryCount);
                if (retry)
                {
                    retryCount++;
                }
                else
                {
                    break;
                }
            }

            if (errorcode == 0)
            {
                Device.PLC.ResetBit(_triggerAddr);
                Device.PLC.SetBit(_triggerAddr);
            }
        }

        public string WaitCompleted()
        {
            var retryCount = 0;
            var errorcode = 0;
            var buf = new byte[BarcodeLength];
            var startTime = DateTime.Now;
            while (true)
            {
                for (int i = 0; i < _dataPorts.Length; i++)
                {
                    try
                    {
                        if (_dataPorts[i].BytesToRead >= BarcodeLength)
                        {
                            _dataPorts[i].Read(buf, 0, buf.Length);
                            return Encoding.ASCII.GetString(buf, 0, buf.Length).Trim('=', '\r', '\n');
                        }
                    }
                    catch (Exception)
                    {
                        Cancel();

                        _dataPorts[i].Close();
                        _dataPorts[i] = null;
                        return null;
                    }
                }
                if (DateTime.Now - startTime > Timeout)
                {
                    errorcode = ErrorCodeHelper.DeviceTimeout;
                    Cancel();
                }

                if (errorcode != 0)
                {
                    var retry = OnCommunicationError(errorcode, retryCount);
                    if (retry)
                    {
                        Trigger();
                        retryCount++;
                        startTime = DateTime.Now;
                    }
                    else
                    {
                        Cancel();
                        return null;
                    }
                }
                
                Thread.Sleep(10);
            }
        }

        public void Cancel()
        {
            Device.PLC.ResetBit(_triggerAddr);
        }
        
        public event CommunicationErrorHandler CommunicationError;

        protected virtual bool OnCommunicationError(int errorcode, int retryCount)
        {
            var handler = CommunicationError;
            if (handler != null)
            {
                return handler.Invoke(this, errorcode, retryCount);
            }

            return false;
        }
    }
}